<!DOCTYPE html>
<html lang="en">
<head>
  <title>Framework Development - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/advanced/framework.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/advanced/framework.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Framework Development</h1>
    <p>During maintaining a number of projects, are your familiar with situations below:</p>
<ul>
<li>Each project contains the same configuration files that need to be copied every time, such as <code>gulpfile.js</code>, <code>webpack.config.js</code>.</li>
<li>Each project has similiar dependancies.</li>
<li>It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have changed?</li>
</ul>
<p>Have your team got:</p>
<ul>
<li>a unified technique selection, such as the choice of databases, templates, frontend frameworks, and middlewares.</li>
<li>a unified default configuration to balance the deviation of different situations, which are not supposed to resolve in code level, like the differences between companies and open communities.</li>
<li>a unified <a href="../core/deployment.html">deployment plan</a> keeping developers concentrate on code without paying attention to deployment details of connecting the framework and platforms.</li>
<li>a unified code style to decrease code's repetition and optimize code's appearance, which is important for a enterprise level framework.</li>
</ul>
<p>To satisfy these demands, Egg endows developers with the capacity of <code>customazing a framework</code>. It is just an abstract layer, which is able to construct a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa.</p>
<p>Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can continuously extracted from these plugins to the framework, which is available to other project by just updating the dependances' version.</p>
<p>See more details in <a href="../tutorials/progressive.html">Progressive Development</a>。</p>
<h2 id="framework-and-multiprocess"><a class="markdown-anchor" href="#framework-and-multiprocess">#</a> Framework And Multiprocess</h2>
<p>The framework extension is applied to Multiprocess Model, as we know <a href="../core/cluster-and-ipc.html">Multiprocess Model</a> and the diffirences between Agent Worker and App Worker, which have diffirent API and both need to inherit.</p>
<p>They both are inherited from <a href="https://github.com/eggjs/egg-core" target="_blank" rel="noopener">EggCore</a>, and Agent is instantiated during the initiation of Agent Worker, while App is instantiated during the initiation of App Worker.</p>
<p>We could think about EggCore as the advanced version of Koa Application, which integrates built-in features such as <a href="./loader.html">Loader</a>、<a href="../basics/router.html">Router</a> and asynchronous launch.</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">      Koa Application</span><br><span class="line">             ^</span><br><span class="line">          EggCore</span><br><span class="line">             ^</span><br><span class="line">      ┌──────┴───────┐</span><br><span class="line">      │              │</span><br><span class="line">  Egg Agent      Egg Application</span><br><span class="line">     ^               ^</span><br><span class="line">agent worker     app worker</span><br></pre></td></tr></table></figure>
<h2 id="how-to-customize-a-framework"><a class="markdown-anchor" href="#how-to-customize-a-framework">#</a> How To Customize A Framework</h2>
<p>To setup by <a href="https://github.com/eggjs/egg-init" target="_blank" rel="noopener">egg-init</a> with the <a href="https://github.com/eggjs/egg-boilerplate-framework" target="_blank" rel="noopener">framework</a> option is a good way, in which generates a scaffold for you.</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ egg-init --<span class="built_in">type</span>=framework yadan</span><br><span class="line">$ <span class="built_in">cd</span> yadan</span><br><span class="line">$ npm i</span><br><span class="line">$ npm <span class="built_in">test</span></span><br></pre></td></tr></table></figure>
<p>But in order to illustrate details, let's do it step by step. Here is the <a href="https://github.com/eggjs/examples/tree/master/framework" target="_blank" rel="noopener">sample code</a>.</p>
<h3 id="framework-api"><a class="markdown-anchor" href="#framework-api">#</a> Framework API</h3>
<p>Each of those APIs is required to be implemented almost twice - one for Agent and another for Application.</p>
<h4 id="eggstartcluster"><a class="markdown-anchor" href="#eggstartcluster">#</a> <code>egg.startCluster</code></h4>
<p>This is the entry function of Egg's multiprocess launcher, based on <a href="https://github.com/eggjs/egg-cluster" target="_blank" rel="noopener">egg-cluster</a>, to start Master, but EggCore running in a single process doesn't invoke this function while Egg does.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> startCluster = <span class="built_in">require</span>(<span class="string">'egg'</span>).startCluster;</span><br><span class="line">startCluster(&#123;</span><br><span class="line">  <span class="comment">// directory of code</span></span><br><span class="line">  baseDir: <span class="string">'/path/to/app'</span>,</span><br><span class="line">  <span class="comment">// directory of framework</span></span><br><span class="line">  framework: <span class="string">'/path/to/framework'</span>,</span><br><span class="line">&#125;, () =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'app started'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>All available options could be found in <a href="https://github.com/eggjs/egg-cluster#options" target="_blank" rel="noopener">egg-cluster</a>.</p>
<h4 id="eggapplication-and-eggagent"><a class="markdown-anchor" href="#eggapplication-and-eggagent">#</a> <code>egg.Application</code> And <code>egg.Agent</code></h4>
<p>These are both singletons but still different with each other. To inherit framework, it's likely to inherited these two classes.</p>
<h4 id="eggappworkerloader-and-eggagentworkerloader"><a class="markdown-anchor" href="#eggappworkerloader-and-eggagentworkerloader">#</a> <code>egg.AppWorkerLoader</code> and <code>egg.AgentWorkerLoader</code></h4>
<p>To customize framework, Loader is required and has to be inherited  from Egg Loader for the propose of either loading directories or rewriting functions.</p>
<h3 id="framework-extension"><a class="markdown-anchor" href="#framework-extension">#</a> Framework Extension</h3>
<p>If we consider a framework as a class, then Egg framework is the base class,and implementing a framework demands to implement entire APIs of Egg.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"name"</span>: <span class="string">"yadan"</span>,</span><br><span class="line">  <span class="string">"dependencies"</span>: &#123;</span><br><span class="line">    <span class="string">"egg"</span>: <span class="string">"^2.0.0"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'./lib/framework.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// lib/framework.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> egg = <span class="built_in">require</span>(<span class="string">'egg'</span>);</span><br><span class="line"><span class="keyword">const</span> EGG_PATH = <span class="built_in">Symbol</span>.for(<span class="string">'egg#eggPath'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Application</span> <span class="keyword">extends</span> <span class="title">egg</span>.<span class="title">Application</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="comment">// return the path of framework</span></span><br><span class="line">    <span class="keyword">return</span> path.dirname(__dirname);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// rewrite Egg's Application</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="built_in">Object</span>.assign(egg, &#123;</span><br><span class="line">  Application,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>The name of framework, default as <code>egg</code>, is a indispensable option to launch an application, set by <code>egg.framwork</code> of <code>package.json</code>, then Loader loads the exported app of a module named it.</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"scripts"</span>: &#123;</span><br><span class="line">    <span class="attr">"dev"</span>: <span class="string">"egg-bin dev"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">"egg"</span>: &#123;</span><br><span class="line">    <span class="attr">"framework"</span>: <span class="string">"yadan"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>As a loadUnit of framework, yadan is going to load specific directories and files, such as <code>app</code> and <code>config</code>. Find more files loaded at <a href="./loader.html">Loader</a>.</p>
<h3 id="princeple-of-framework-extension"><a class="markdown-anchor" href="#princeple-of-framework-extension">#</a> Princeple of Framework Extension</h3>
<p>The path of framework is set as a varible named as `Symbol.for('egg#eggPath') to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain.</p>
<p>Given a triple-layer framework: department level &gt; enterprise level &gt; Egg</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// enterprise</span></span><br><span class="line"><span class="keyword">const</span> Application = <span class="built_in">require</span>(<span class="string">'egg'</span>).Application;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Enterprise</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'/path/to/enterprise'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Customize Application</span></span><br><span class="line">exports.Application = Enterprise;</span><br><span class="line"></span><br><span class="line"><span class="comment">// department</span></span><br><span class="line"><span class="keyword">const</span> Application = <span class="built_in">require</span>(<span class="string">'enterprise'</span>).Application;</span><br><span class="line"><span class="comment">// extend enterprise's Application</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">department</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'/path/to/department'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// the path of `department` have to be designated as described above</span></span><br><span class="line"><span class="keyword">const</span> Application = <span class="built_in">require</span>(<span class="string">'department'</span>).Application;</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Application();</span><br><span class="line">app.ready();</span><br></pre></td></tr></table></figure>
<p>These code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to <a href="../core/development.html">development</a>和<a href="../core/deployment.html">deployment</a>.</p>
<h3 id="custom-agent"><a class="markdown-anchor" href="#custom-agent">#</a> Custom Agent</h3>
<p>Egg's mutilprocess model is composed of Application and Agent. Therefore Agent, another fundamental class similiar to Application, is also required to be implemented.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// lib/framework.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> egg = <span class="built_in">require</span>(<span class="string">'egg'</span>);</span><br><span class="line"><span class="keyword">const</span> EGG_PATH = <span class="built_in">Symbol</span>.for(<span class="string">'egg#eggPath'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Application</span> <span class="keyword">extends</span> <span class="title">egg</span>.<span class="title">Application</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="comment">// return the path of framework</span></span><br><span class="line">    <span class="keyword">return</span> path.dirname(__dirname);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Agent</span> <span class="keyword">extends</span> <span class="title">egg</span>.<span class="title">Agent</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="keyword">return</span> path.dirname(__dirname);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// rewrite Egg's Application</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="built_in">Object</span>.assign(egg, &#123;</span><br><span class="line">  Application,</span><br><span class="line">  Agent,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>** To be careful about that Agent and Application based on the same Class possess different APIs. **</p>
<h3 id="custom-loader"><a class="markdown-anchor" href="#custom-loader">#</a> Custom Loader</h3>
<p>Loader, the core of the launch process, is capable of loading data code, adjusting loading orders or even strengthen regulation of code.</p>
<p>As the same as Egg-Path, Loader exposes itself at <code>Symbol.for('egg#loader')</code> to ensuer it's accessibility on prototype chain.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// lib/framework.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> egg = <span class="built_in">require</span>(<span class="string">'egg'</span>);</span><br><span class="line"><span class="keyword">const</span> EGG_PATH = <span class="built_in">Symbol</span>.for(<span class="string">'egg#eggPath'</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">YadanAppWorkerLoader</span> <span class="keyword">extends</span> <span class="title">egg</span>.<span class="title">AppWorkerLoader</span> </span>&#123;</span><br><span class="line">  load() &#123;</span><br><span class="line">    <span class="keyword">super</span>.load();</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Application</span> <span class="keyword">extends</span> <span class="title">egg</span>.<span class="title">Application</span> </span>&#123;</span><br><span class="line">  get [EGG_PATH]() &#123;</span><br><span class="line">    <span class="comment">// return the path of framework</span></span><br><span class="line">    <span class="keyword">return</span> path.dirname(__dirname);</span><br><span class="line">  &#125;</span><br><span class="line">    <span class="comment">// supplant default Loader</span></span><br><span class="line">  get [EGG_LOADER]() &#123;</span><br><span class="line">    <span class="keyword">return</span> YadanAppWorkerLoader;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// rewrite Egg's Application</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="built_in">Object</span>.assign(egg, &#123;</span><br><span class="line">  Application,</span><br><span class="line">  <span class="comment">// custom Loader, a dependence of the high level frameword, needs to be exported.</span></span><br><span class="line">  AppWorkerLoader: YadanAppWorkerLoader,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>AgentworkerLoader is not going to be described because of it's similarity of AppWorkerLoader, but be aware of it's located at <code>agent.js</code> instand of <code>app.js</code>.</p>
<h2 id="the-principle-of-launch"><a class="markdown-anchor" href="#the-principle-of-launch">#</a> The principle of Launch</h2>
<p>Many descriptions of launch process are scattered at <a href="../core/cluster-and-ipc.html">Mutilprocess Model</a>, <a href="./loader.html">Loader</a> and <a href="./plugin.html">Plugin</a>, and here is a summarization.</p>
<ul>
<li><code>startCluster</code> is invoked with <code>baseDir</code> and <code>framework</code>, then Master process is launched.</li>
<li>Master forks a new process as Agent Worker
<ul>
<li>instantiate Agent Class of the framework loaded from path passed by the <code>framework</code> param.</li>
<li>Agent finds out the AgentWorkerLoader and then starts to load</li>
<li>use AgentWorkerLoader to load Worker synchronously in the sequence of Plugin Config, Extend, <code>agent.js</code> and other files.</li>
<li>The initiation of <code>agent.js</code> is able to be customized, and it supports asynchronous launch after which it notifies Master and invoke the function passed to <code>beforeStart</code>.</li>
</ul>
</li>
<li>After recieving the message that Agent Worker is launched，Master forks App Workers by cluster.
<ul>
<li>App Workers are mutilple identical processes launched simultaneously</li>
<li>App Worker is instantiated, which is similiar to Agent inherited Application class of framework loaded from framework path.</li>
<li>The same as Agent, Loading process of Application starts with AppWorkerLoader which loads files in the same order and finally informed Master.</li>
</ul>
</li>
<li>After informed of luanching successfully of each App Worker, Master is finally functioning.</li>
</ul>
<h2 id="framework-testing"><a class="markdown-anchor" href="#framework-testing">#</a> Framework Testing</h2>
<p>You'd better read <a href="../core/unittest.html">unittest</a> first, which is similiar to framework testing in a quantity of situations.</p>
<h3 id="initiation"><a class="markdown-anchor" href="#initiation">#</a> Initiation</h3>
<p>Here are some differences between initiation of frameworks.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mock = <span class="built_in">require</span>(<span class="string">'egg-mock'</span>);</span><br><span class="line">describe(<span class="string">'test/index.test.js'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> app;</span><br><span class="line">  before(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    app = mock.app(&#123;</span><br><span class="line">      <span class="comment">// test/fixtures/apps/example</span></span><br><span class="line">      baseDir: <span class="string">'apps/example'</span>,</span><br><span class="line">      <span class="comment">// importent !! Do not miss</span></span><br><span class="line">      framework: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> app.ready();</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  after(<span class="function"><span class="params">()</span> =&gt;</span> app.close());</span><br><span class="line">  afterEach(mock.restore);</span><br><span class="line"></span><br><span class="line">  it(<span class="string">'should success'</span>, () =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> app.httpRequest()</span><br><span class="line">    .get(<span class="string">'/'</span>)</span><br><span class="line">    .expect(<span class="number">200</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<ul>
<li>Different from application testing, framework testing tests framework code instead of application code, so that baseDir varies for the propose of testing kinds of applications.</li>
<li>BaseDir is potentially considered to be under the path of <code>test/fixtures</code>, otherwise it should be absolute paths.</li>
<li>The <code>framework</code> option is indispensable, which could be a absolute path or <code>true</code> meaning the path of the framework to be current directory.</li>
<li>The use of the app should wait for the <code>ready</code> event in <code>before</code> hook, or some of the APIs is not avaiable.</li>
<li>Do not forget to invoke <code>app.close()</code> after testing, which could arouse the exhausting of fds, caused by unclosed log files.</li>
</ul>
<h3 id="cache"><a class="markdown-anchor" href="#cache">#</a> Cache</h3>
<p><code>mm.app</code> enables cache as default, which means new envoriment setting would not work once loaded.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mock = <span class="built_in">require</span>(<span class="string">'egg-mock'</span>);</span><br><span class="line">describe(<span class="string">'/test/index.test.js'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> app;</span><br><span class="line">  afterEach(<span class="function"><span class="params">()</span> =&gt;</span> app.close());</span><br><span class="line"></span><br><span class="line">  it(<span class="string">'should test on local'</span>, () =&gt; &#123;</span><br><span class="line">    mock.env(<span class="string">'local'</span>);</span><br><span class="line">    app = mock.app(&#123;</span><br><span class="line">      baseDir: <span class="string">'apps/example'</span>,</span><br><span class="line">      framework: <span class="literal">true</span>,</span><br><span class="line">      cache: <span class="literal">false</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> app.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">  it(<span class="string">'should test on prod'</span>, () =&gt; &#123;</span><br><span class="line">    mock.env(<span class="string">'prod'</span>);</span><br><span class="line">    app = mock.app(&#123;</span><br><span class="line">      baseDir: <span class="string">'apps/example'</span>,</span><br><span class="line">      framework: <span class="literal">true</span>,</span><br><span class="line">      cache: <span class="literal">false</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> app.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="multipleprocess-testing"><a class="markdown-anchor" href="#multipleprocess-testing">#</a> Multipleprocess Testing</h3>
<p>Mutilprocess is rarely tested because of the high cost and the unavailability of API level's mock, meanwhile, processes have a slow start or even timeout, but it still remains the most effective way of testing multiprocess model.</p>
<p>The option of <code>mock.cluster</code> have no difference with <code>mm.app</code> while their APIs are totally distinct, however, SuperTest still works.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mock = <span class="built_in">require</span>(<span class="string">'egg-mock'</span>);</span><br><span class="line">describe(<span class="string">'/test/index.test.js'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> app;</span><br><span class="line">  before(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    app = mock.cluster(&#123;</span><br><span class="line">      baseDir: <span class="string">'apps/example'</span>,</span><br><span class="line">      framework: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> app.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">  after(<span class="function"><span class="params">()</span> =&gt;</span> app.close());</span><br><span class="line">  afterEach(mock.restore);</span><br><span class="line">  it(<span class="string">'should success'</span>, () =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> app.httpRequest()</span><br><span class="line">    .get(<span class="string">'/'</span>)</span><br><span class="line">    .expect(<span class="number">200</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>Tests of <code>stdout/stderr</code> are also avaiable, since <code>mm.cluster</code> is based on <a href="https://github.com/popomore/coffee" target="_blank" rel="noopener">coffee</a> in which multiprocess testing is supported.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mock = <span class="built_in">require</span>(<span class="string">'egg-mock'</span>);</span><br><span class="line">describe(<span class="string">'/test/index.test.js'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> app;</span><br><span class="line">  before(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    app = mock.cluster(&#123;</span><br><span class="line">      baseDir: <span class="string">'apps/example'</span>,</span><br><span class="line">      framework: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> app.ready();</span><br><span class="line">  &#125;);</span><br><span class="line">  after(<span class="function"><span class="params">()</span> =&gt;</span> app.close());</span><br><span class="line">  it(<span class="string">'should get `started`'</span>, () =&gt; &#123;</span><br><span class="line">    <span class="comment">// set the expectation of console</span></span><br><span class="line">    app.expect(<span class="string">'stdout'</span>, /started/);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/advanced/framework.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/advanced/framework.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
